// 待完成

package nachos.vm;

import nachos.machine.*;
import nachos.userprog.*;

import java.util.HashMap;
import java.util.LinkedList;

/**
 * 支持按需分页的 UserProcess
 */
public class VMProcess extends UserProcess {
    /**
     * 分配一个新进程
     */
    public VMProcess() {
        super();
        lazySectionList = new HashMap<>();
        lazyStackList = new LinkedList<>();
    }

    /**
     * 保存此进程的状态以准备上下文切换
     * 由 UThread.saveState() 调用
     */
    public void saveState() {
        VMKernel.copyTLBEntryStatus();
    }

    /**
     * 在上下文切换后还原此进程的状态
     * 由 UThread.restoreState() 调用
     */
    public void restoreState() {
    }

    public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
        // 偏移量和长度非负，且不能越界
        Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);

        // 获取物理内存
        byte[] memory = Machine.processor().getMemory();

        // 传输的字节数过多，会导致虚拟内存越界
        if (length > pageSize * numPages - vaddr) {
            // 截取不超过越界的部分
            length = pageSize * numPages - vaddr;
        }

        // 不断读取虚拟内存，直到写完指定长度的数据
        int successRead = 0;
        while (successRead < length) {
            // 计算页号
            int vpn = Processor.pageFromAddress(vaddr + successRead);

            // 检查是否越界
            if (vpn < 0 || vpn >= numPages) {
                return successRead;
            }

            // 计算页偏移量
            int pageOffset = Processor.offsetFromAddress(vaddr + successRead);

            // 计算当页剩余容量
            int pageRemain = pageSize - pageOffset;

            // 比较未读取的内容与当页未使用的空间，取较小值用于数据转移
            int amount = Math.min(length - successRead, pageRemain);

            // 获取页表项
            TranslationEntry entry = getPage(vpn);

            // 计算真实地址
            int readAddress = entry.ppn * pageSize + pageOffset;

            // 将数据从指定数组复制到内存
            System.arraycopy(memory, readAddress, data, offset + successRead, amount);

            // 页标记
            entry.used = true;

            // 成功读取的数据量
            successRead += amount;
        }

        return successRead;
    }

    public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) {
        // 偏移量和长度非负，且不能越界
        Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);

        // 获取物理内存
        byte[] memory = Machine.processor().getMemory();

        // 传输的字节数过多，会导致虚拟内存越界
        if (length > pageSize * numPages - vaddr) {
            // 截取不超过越界的部分
            length = pageSize * numPages - vaddr;
        }

        // 不断写入虚拟内存，直到写完指定长度的数据
        int successWrite = 0;
        while (successWrite < length) {
            // 计算页号
            int vpn = Processor.pageFromAddress(vaddr + successWrite);

            // 检查是否越界
            if (vpn < 0 || vpn >= numPages) {
                return successWrite;
            }

            // 计算页偏移量
            int pageOffset = Processor.offsetFromAddress(vaddr + successWrite);

            // 计算当页剩余容量
            int pageRemain = pageSize - pageOffset;

            // 比较未读取的内容与当页未使用的空间，取较小值用于数据转移
            int amount = Math.min(length - successWrite, pageRemain);

            // 获取页表项
            TranslationEntry entry = getPage(vpn);

            // 计算真实地址
            int realAddress = entry.ppn * pageSize + pageOffset;

            // 如果当前页为只读状态，终止数据转移
            if (pageTable[vpn].readOnly) {
                continue;
            }

            // 将数据从指定数组复制到内存
            System.arraycopy(data, offset + successWrite, memory, realAddress, amount);

            // 页标记
            entry.used = true;
            entry.dirty = true;

            // 成功写入的数据量
            successWrite = successWrite + amount;
        }

        return successWrite;
    }

    /**
     * 初始化此进程的页表，以便可执行文件可以按需分页
     *
     * @return true 如果成功
     */
    protected boolean loadSections() {
        int top = 0;

        // 加载用户程序到内存
        for (int s = 0; s < coff.getNumSections(); s++) {
            CoffSection section = coff.getSection(s);

            Lib.debug(dbgProcess, "\tinitializing " + section.getName()
                    + " section (" + section.getLength() + " pages)");

            top += section.getLength();

            for (int i = 0; i < section.getLength(); i++) {
                int vpn = section.getFirstVPN() + i;
                // 置于懒加载列表中
                lazySectionList.put(vpn, new lazySection(section, i));
            }
        }

        for (; top < numPages - 1; top++) {
            // 置于懒加载列表中
            lazyStackList.add(top);
        }

        return true;
    }

    /**
     * 释放 loadSections() 分配的任何资源
     */
    protected void unloadSections() {
        VMKernel.releaseAllPage(processId, numPages);
    }

    /**
     * 处理用户异常
     * 由 UserKernel.exceptionHandler() 调用
     * cause 参数标识发生的异常
     * 请参阅 Processor.exceptionZZZ 常量
     *
     * @param cause 发生的用户异常
     */
    public void handleException(int cause) {
        Processor processor = Machine.processor();

        switch (cause) {
            // 如果虚拟页到物理页的转换不在 TLB 中
            case Processor.exceptionTLBMiss:
                handleTLBMiss(processor.readRegister(Processor.regBadVAddr));
                break;
            default:
                super.handleException(cause);
                break;
        }
    }

    // 将转换加载到 TLB 中
    private void handleTLBMiss(int addr) {
        // 获取页号
        int vpn = Processor.pageFromAddress(addr);

        // 获取页
        TranslationEntry entry = getPage(vpn);

        // 是否完成替换
        boolean isFinishReplace = false;

        // 获取 TLB 长度
        int TLBSize = Machine.processor().getTLBSize();

        // TLB 中的页
        TranslationEntry TLBPage;

        // 查看 TLB 中是否有需要优先替换的
        for (int i = 0; i < TLBSize; i++) {
            TLBPage = Machine.processor().readTLBEntry(i);
            if (entry.ppn == TLBPage.ppn || !TLBPage.valid) {
                // 完成 TLB 替换
                entry.valid = true;
                Machine.processor().writeTLBEntry(i, entry);
                isFinishReplace = true;
                break;
            }
        }

        // 如果此时还未完成替换，就采用随机替换
        if (!isFinishReplace) {
            // 获取 TLB 中的任意页
            int i = Lib.random(TLBSize);
            TLBPage = Machine.processor().readTLBEntry(i);

            if (TLBPage.used || TLBPage.dirty) {
                // 将 TLB 中指定页的状态同步到页表
                VMKernel.copyTLBEntryStatus(i);
                return;
            }

            // 完成 TLB 替换
            Machine.processor().writeTLBEntry(i, entry);
        }
    }

    // 获取一个页表项
    private TranslationEntry getPage(int vpn) {
        TranslationEntry entry;

        // 如果该可执行文件的页还未加载
        if (lazySectionList.containsKey(vpn)) {
            // 获取一个空闲页
            entry = VMKernel.getFreePage(processId, vpn);

            // 将可执行文件的页读入内存
            lazySectionList.remove(new Integer(vpn)).load(entry.ppn);
            return entry;
        }

        // 如果该栈文件的页还未加载
        if (lazyStackList.contains(vpn)) {
            entry = VMKernel.getFreePage(processId, vpn);
            entry.readOnly = false;

            lazyStackList.remove(new Integer(vpn));
            return entry;
        }

        // 从页表中获取
        entry = VMKernel.getPageFromMemory(this.processId, vpn);
        if (entry == null) {
            // 从交换区中获取
            entry = VMKernel.getPageFromSwap(this.processId, vpn);
        }

        return entry;
    }

    private static final int pageSize = Processor.pageSize;
    private static final char dbgProcess = 'a';
    private static final char dbgVM = 'v';

    // 懒加载，可执行文件的页
    protected HashMap<Integer, lazySection> lazySectionList;

    // 懒加载，栈的页
    protected LinkedList<Integer> lazyStackList;

    private class lazySection {
        private CoffSection section;
        private int spn;

        private lazySection(CoffSection section, int spn) {
            this.spn = spn;
            this.section = section;
        }

        // 加载页
        private void load(int ppn) {
            section.loadPage(spn, ppn);
        }
    }
}
